// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: ziyan

#include <cxxopts.hpp>

#include "coin/proto/coin_order_gateway.pb.h"
#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/log.h"
#include "coin2/base/time.h"
#include "coin2/strategy/strategy.h"

#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include <rapidjson/filereadstream.h>

using namespace coin2::exchange::base::symbology;
using namespace coin2::base::config;
using namespace coin2::exchange::feed;
using namespace coin2::app;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::order::OrderUpdate;

class PrintStrategy : public ::coin2::strategy::IStrategy {
  void onTradeFeed(const FeedUpdate& upd) override {
    // auto& trade = upd.trade();
    handleData(
        upd.product().relative_norm(),
        upd.trade().timestamp == 0 ? upd.ts().post_read_timestamp() : upd.trade().timestamp,
        upd.trade().price);
  }
  void onBookFeed(const FeedUpdate& upd) override {
    auto& book = upd.book();
    if (book.Ask0().has_value() || book.Bid0().has_value()) {
      handleData(
          upd.product().relative_norm(),
          upd.book().Timestamp() == 0 ? upd.ts().post_read_timestamp() : upd.book().Timestamp(),
          -1.0);
    }
  }

  void Init(Driver* driver) override {
    auto config = const_cast<DriverConfig&>(driver->config());
    auto exchangesMap = config.mutable_feed()->mutable_exchanges();
    for (auto& [mea, configs] : *exchangesMap) {
      auto norms = configs.mutable_products()->mutable_norms();
      if (this->mea == "") {
        this->mea = mea;
      }
      for (auto& norm : *norms) {
        captureTime[norm] = -1;
        checkProducts.insert(norm);
        feeddataAmount[norm] = 0;
        openPrice[norm] = -1;
      }
    }
  }
  
  private:
  std::unordered_set<std::string> checkProducts;
  std::map<std::string,int64_t> captureTime;
  std::map<std::string,int64_t> feeddataAmount;
  std::map<std::string,double> openPrice;
  std::string mea = "";

  void handleData(std::string symbol,int64_t timestamp,double price){
    if (checkProducts.find(symbol) != checkProducts.end()) {
      feeddataAmount[symbol]++;
      if (captureTime[symbol] < 0) {
        captureTime[symbol] = timestamp;
      } else {
        captureTime[symbol] = std::min(captureTime[symbol], timestamp);
      }
      if (openPrice[symbol] < 0 && price > 0){
        openPrice[symbol] = price;
      }
      LOG(INFO) << symbol << ':' << Iso8601FromTimestamp(captureTime[symbol]) << ':' << timestamp;
      // 5min after start tarding
      if (timestamp - captureTime[symbol] > 300000000000) {
        if (feeddataAmount[symbol]>5){
          checkProducts.erase(symbol);
        } else {
          // discontinuous feed , may give some issue feed before trading start
          captureTime[symbol] = timestamp;
          feeddataAmount[symbol] = 1;
        }
        
      }
    }
    if (checkProducts.empty()){
      writeToFile();
      exit(0);
    }
  }

  void writeToFile(std::string path = "../ops_file/symbol_verify_log.json") {
    std::fstream outfile;
    rapidjson::Document doc;
    std::unique_ptr<char[]> buf(new char[32768]);
    FILE* fp = fopen(path.c_str(), "r");
    if (!fp) {
      rapidjson::StringStream s("{}");
      doc.ParseStream(s);
    } else {
      rapidjson::FileReadStream is(fp, buf.get(), 32768);
      doc.ParseStream(is);
      fclose(fp);
      if (doc.HasParseError()) {
      rapidjson::StringStream s("{}");
      doc.ParseStream(s);
      }
    }

    rapidjson::Document::AllocatorType& allocator = doc.GetAllocator();
    
    rapidjson::Value meaObject(rapidjson::kObjectType);
    for(auto it = this->captureTime.begin(); it!= this->captureTime.end();++it){
      rapidjson::Value timestamp,time,dataAmount,symbol,price;
      rapidjson::Value productObject(rapidjson::kObjectType);
      timestamp.SetInt64(it->second);
      time.SetString(Iso8601FromTimestamp(it->second).c_str(),allocator);
      dataAmount.SetInt(this->feeddataAmount[it->first]);
      price.SetDouble(this->openPrice[it->first]);
      symbol.SetString(it->first.c_str(),allocator);
      productObject.AddMember("timestamp",timestamp,allocator);
      productObject.AddMember("time",time,allocator);
      productObject.AddMember("data_amount_first_5min",dataAmount,allocator);
      productObject.AddMember("trade_open_price",price,allocator);
      meaObject.AddMember(symbol,productObject,allocator);
    }
    rapidjson::Value meaValue;
    meaValue.SetString(this->mea.c_str(),allocator);
    if (doc.HasMember(meaValue)){
      doc.RemoveMember(meaValue);
    }
    doc.AddMember(meaValue,meaObject,allocator);

    rapidjson::StringBuffer strBuf;
    rapidjson::Writer<rapidjson::StringBuffer> writer(strBuf);
    doc.Accept(writer);

    std::string jsonstr = strBuf.GetString();
    outfile.open(path,std::ios::out);
    outfile << jsonstr << std::endl;
    std::cout << jsonstr << std::endl;
    outfile.close();
  }
};

int main(int argc, char* argv[]) {
  google::InitGoogleLogging(argv[0]);

  DriverConfig app;

  cxxopts::Options opt("Printer", "Print feed and order events.");
  AddDriverOptions(&opt);
  AddIntervalOptions(&opt);
  AddProductOptions(&opt);

  auto res = opt.parse(argc, argv);
  ParseDriverOptions(res, &app);
  ParseIntervalOptions(
      res,
      app.mutable_feed()->mutable_common()->mutable_archive()->mutable_interval());
  ParseProductOptions(res, app.mutable_feed());

  LOG(INFO) << app.DebugString();

  PrintStrategy printer;  // printer unused if feed-writer
  Driver driver(app, &printer);
  driver.Run();

  return 0;
}
